"""Classes for the operation of realtime streaming.

    
RealtimeManager: wraps an infoGetter, Streamer, and an Min1Recorder    

"""

from PyQt4.QtCore import *
import datetime
from misc import misc
import threading
import time
import infogetter
import threads
import navalasystem as na
from agent import recorder
import threads
import tradezone
from misc import timedata
import misc.quoteperiod as qp
from misc.parts import StockerEx
from misc import minutegrouper

_INITIAL = 999
_INIT_IG = 1000
_INIT_STREAMER = 1001
_INIT_STARTRECORDER = 1002
_INIT_StartStreaming = 1004
_WATCH = 2000

class RealtimeManager(threads.LoopThread, StockerEx):
    """Combined realtime and history (min1) recording of some stocks.
    
    An Min1Recorder is kept in perpetual loop and controlled from here with
    these messages: pause, setnames, reinitiate."""

    # callable InfoGetter getter
    f_igg = None
    # callable Streamer getter
    f_streamer = None
    # =60. Period to wake up recorder (in seconds)
    recorderPeriod = 30 # soh pra ver o comportamento
    # =5. Period of reading streamer for new data (in seconds).
    streamerPeriod = 5
    
    ## Internal states
    # infogetter.InfoGetter instance
    _infoGetter = None
    # Streamer instance
    _streamer = None
    # Min1Recorder instance
    _recorder = None
    # Flags that setting Streamer names is pending
    _flagPendingSetStreamerNames = False
    # Flags a penging StartStreaming()
    _flagPendingStartStreaming = False
    # Flags the need to check InfoGetter proxy connection
    _flagCheckIG = False
    # Flags the need to check Streamer proxy connection
    _flagCheckStreamer = False
    # Whether Realtime streaming was started. Used to stop streaming before
    # exit, in case it is streaming
    _flagStreaming = False
    # Whether Recorder agent was started
    _flagRecorder = False
    # Whether Recorder agent was started
    _flagSetRecorderNames = False
    # General state of state machine
    _state = _INITIAL
    # Whether has waken up the Recorder with closed market
    _flagDidWithClosedMarket = False
    # Set to True when receives "finished" message from Recorder,
    # and to False when Recorder is reinitiated
    _flagRecorderFinished = False
    # Flags whether Quotes transfer was already performed since the
    # Recorder has finished
    _flagTransferedMin1 = False
    # Whether there was a malsucceeded pause
    _flagPendingPause = False
    # Pending pause data
    _pendingPauseData = None
    # Whether RealtimeManager is paused
    _flagPaused = False
    # Time when recorder was last waken up
    _recorderTic = -1
    # Time when stream was last read
    _streamerTic = -1

    

    def __init__(self, name="Realtime Manager", *args, **kwargs):
        super(RealtimeManager, self).__init__(name=name, *args, **kwargs)
        # TODO: Make this more "independent"
        self.f_igg = tradezone.GetHTTPClientProxy
        self.f_streamer = tradezone.GetStreamerProxy
        
    ###############################################################################
    ## Overwritten stuff

    def _OnUpdateAllNamesEx(self):
        """This one deals with the recorder names."""
        self.logger.info("_allNamesEx = %s" % (self._allNamesEx))
        # This is reset because every time the user changes the watched
        # securities, some of the watched ones may still have incomplete
        # intraday data. Therefore, we will "do it" even if the market is closed
        # at least once
        self._flagDidWithClosedMarket = False
        if self._recorder is None:
            # Realtime manager is being manipulated before the relevant objects
            # have been created properly, but will flag the need to complete the
            # "request" when possible.
            self._flagSetRecorderNames = True
            return
        self._SetRecorderNames()
        
    def _OnUpdateAllNames(self):
        """This one deals with the Streamer names."""
        self.logger.info("_allNames = %s" % (self._allNames))
        if self._recorder is None:
            # Realtime manager is being manipulated before the relevant objects
            # have been created properly, but will flag the need to complete the
            # "request" when possible.
            self._flagPendingSetStreamerNames = True
            return
        self._SetStreamerNames()
        
    def _TreatQueueItem(self, token, data):
        """Treats "StartStreaming", "StopStreaming", ".
        
        ("StartStreaming", dummy) -- Calls _StartStreaming().
        ("StopStreaming", dummy) -- Calls _StopStreaming().
        ("Pause", flagYes) -- pauses/unpauses "Normal action", which is
            managing the Recorder and Getting quotes. Temporarily tell streamer
            to stop getting quotes from TZDC server.

        """
        if token == "StartStreaming":
            if self._state == _WATCH:
                self._StartStreaming()
            else:
                self.logger.info("Cannot START realtime streaming, not in WATCH state")
        elif token == "StopStreaming":
            if self._state == _WATCH:
                self._StopStreaming()
            else:
                self.logger.info("Cannot STOP realtime streaming, not in WATCH state")
        elif token == "Pause":
            self._DoPause(data)
        else:
            super(self.__class__, self)._TreatQueueItem(token, data)

    # *-*-*-*-*-*-*-*-
    # *-*-*-*-*-*-*-*-
    def _Cycle(self):
        if self._state == _INITIAL: self._state = _INIT_IG
        if self._state == _INIT_IG:
            if self._CheckIG():
                self._state = _INIT_STREAMER
            else:
                self.logger.exception("Failed getting InfoGetter Proxy. WILL TRY AGAIN!")
                self.Sleep(3)
        if self._state == _INIT_STREAMER:
            if self._CheckStreamer():
                self._state = _INIT_STARTRECORDER
            else:
                self.logger.exception("Failed getting Streamer Proxy. WILL TRY AGAIN!")
                self.Sleep(3)
        if self._state == _INIT_STARTRECORDER:
            if self._StartRecorder():
                self._state = _INIT_StartStreaming
            else:
                self.logger.exception("Failed starting recorder. WILL TRY AGAIN!")
                self.Sleep(3)
        if self._state == _INIT_StartStreaming:
            if self._StartStreaming():
                self._state = _WATCH
            else:
                self.logger.exception("Failed to start realtime streaming. WILL TRY AGAIN!")
                self.Sleep(3)

        if self._state != _WATCH:
            return
            
        # Pending operations
        if self._flagCheckIG:
            self._CheckIG()
        if self._flagCheckStreamer:
            self._CheckStreamer()
        if self._flagSetRecorderNames:
            self._SetRecorderNames()
        if self._flagPendingSetStreamerNames:
            self._SetStreamerNames()
        if self._flagPendingPause:
            self._DoPause(self._pendingPauseData)
        if self._flagPendingStartStreaming:
            self._StartStreaming()
        
        # *** "Normal" Operation
        
        if not self._flagPaused:
            if self._flagRecorderFinished and not self._flagTransferedMin1:
                self._TransferMin1()
                
            # Attention to Recorder
            tic = time.time()
            if self._recorderTic < 0:
                self._recorderTic = tic
            elif tic-self._recorderTic >= self.recorderPeriod:
                self._attention_Recorder()
                self._recorderTic = tic        
            # Attention to Streamer
            tic = time.time()
            if self._streamerTic < 0:
                self._streamerTic = tic
            elif tic-self._streamerTic >= self.streamerPeriod:
                self._attention_Streamer()
                self._streamerTic = tic        
            
        self.Sleep(1)
            
    def _BeforeExit(self):
        """Reimplementation of LoopThread._BeforeExit()."""
        if self._flagStreaming:
            if not self._StopStreaming():
                self.logger.warning("Failed to stop realtime streaming")
        # Note: Exiting recorder is not necessary, because the System will do
        # it anyway


    ###########################################################################
    ## The following routines are called by _Cycle() and have same
    ## Exception policy, which is:
    ##  - catch log Exceptions;
    ##  - return True if no Exception occurred; False otherwise.

    def _attention_Recorder(self):
        """Gives attention to the Recorder. From the "attention" series.
        
        The Recorder is waken up (according to _recorderPeriod) to read new
        quotes.
        """
        # This routine shouldn't raise an Exception, unless there is a bug
        if not self._flagRecorder or self._flagSetRecorderNames:
            return # Recorder is not working or not properly set
        # Waking up Recorder...
        flagOpen = na.vala.flagOpenMarket
        if flagOpen or not self._flagDidWithClosedMarket:
            if not self._flagRecorderFinished:
                self.logger.debug("Wanted to reinitiate recorder, but it is still working")
            else:
                self._flagRecorderFinished = False
                self._recorder.PutMessage("Pause", False)
                self._recorder.PutMessage("Reinitiate")
                if not flagOpen:
                    self._flagDidWithClosedMarket = True

    def _attention_Streamer(self):
        """Gives attention to the Streamer. From the "attention" series.
        
        Tries to read and record quotes.
        """
        if self._flagCheckStreamer:
            return # Does not have a valid Streamer proxy
        tic = time.time()
        try:
            quotess = self._streamer.GetQuotess()
        except:
            self.logger.exception("Error reading quotes")
            self._flagCheckStreamer = True
            return
        try:
            if not misc.isempty(quotess):
#                self.logger.debug("AEEEEEEEEEEEE *************")
                self.logger.info("STREAM: Got Quotess with keys %s" % quotess.keys())
                # Quote recording
                for name in quotess:
                    Q = quotess[name]
                    self.logger.debug("STREAM: Recording %d quotes for %s (1st time is %s)..." % (len(Q), name, misc.ts2dt(Q.timestamp[0])))
                    na.vala.db.quote_Record(qp.secondly, name, Q)
                    self.logger.debug("...Done")
                # Signal emiting
                na.vala.EmitNewData({"names": quotess.keys(), \
                    "periods": [qp.secondly]})
###            else:
###                self.logger.debug("STREAM: Empty quotess")
        except:
            self.logger.exception("Error recording quotes (which were ignored)")
#        self.logger.debug("Took %.3g seconds to give attention to Streamer" % (time.time()-tic))


    def _CheckIG(self):
        """TODO: This idea should probably become a reason to implement a separate class to use in multiple inheritance whenever a class needs an InfoGetter."""
        result = True
        try:
            if not self._infoGetter:
                self._infoGetter = self.f_igg()
            if self._infoGetter.Ping() != "pong":
                raise RuntimeError("Couldn't ping infoGetter")
            self.logger.info("Got a valid infogetter")
            self._flagCheckIG = False
        except:
            self.logger.exception("Couldn't get a valid infoGetter proxy!")
            self._flagCheckIG = True
            result = False
        return result
    
    def _CheckStreamer(self):
        result = True
        try:
            if not self._streamer:
                self._streamer = self.f_streamer()
            if self._streamer.Ping2() != "pong":
                raise RuntimeError("Couldn't ping Streamer")
            self.logger.info("Got a valid Streamer")
            self._flagCheckStreamer = False
        except:
            self.logger.exception("Couldn't get a valid Streamer proxy!")
            self._flagCheckStreamer = True
            result = False
        return result
    
    def _StartRecorder(self):
        """Creates and starts a Min1Recorder."""
        result = True
        try:
            r = recorder.Min1Recorder(name='MyTrueSelf')
            r.flagReinitiateWhenFinished = True # Will run forever
            r.flagPauseWhenFinished = True
            # Now I want the recorder to start again every now and then r.flagRememberRecordedStocks = True
            r.flagTellNewData = True
            r.stockNames = [] # Will add names later
            r.infoGetter = self.f_igg() # TODO: implement a f_igg at the Recorder, although I am not having much problem with this ATM
            r.Reinitiate() # Reinitiates state regardless of what is registered as data in the db
            r.Start(self.on_RecorderMessage)
            self._recorder = r
            self._flagRecorder = True
            self.logger.info("Successfully started a Recorder agent named ``%s``" % r.name)
        except:
            self.logger.exception("Failed to start Recorder")
            result = False
        return result

    def _SetStreamerNames(self):
        """Tries to set Streamer names; flags retry if unsuccessful."""
        result = True
        try:
            self._streamer.SetNames(self._allNames)
            self._flagPendingSetStreamerNames = False
            self.logger.info("Infogetter's names set to %s" % self._allNames)
        except:
            self.logger.exception("Couldn't set Streamer names")
            self._flagPendingSetStreamerNames = True
            self._flagCheckStreamer = True
            result = False
        return result

    def _SetRecorderNames(self):
        """Sends "setnames" message to Recorder*.
        
        *This will take effect only next time the Recorder is reinitiated.
        """
        result = True
        try:
            self._recorder.PutMessage("SetNames", self._allNamesEx)
            self._flagSetRecorderNames = False
        except:
            self.logger.exception("Couldn't set recorder names, will retry later")
            self._flagSetRecorderNames = True
            result = False
        return result
        
    def _StartStreaming(self):
        result = True
        try:
            # maybe TODO: At the moment, there is no check for the success of the operation
            # The only error that can occur is the Streamer proxy is broken
            self._streamer.Pause(False)
            self._flagStreaming = True
            self._flagPendingStartStreaming = False
            self.logger.info("Started realtime streaming")
        except:
            self.logger.exception("Couldn't start realtime streaming")
            self._flagPendingStartStreaming = True
            self._flagCheckStreamer = True
            result = False
        return result
        
    def _StopStreaming(self):
        result = True
        try:
            self.logger.info("Will try to stop realtime streaming")
            self._streamer.Pause(True)
            self._flagStreaming = False
            self.logger.info("Stopped realtime streaming")
        except:
            self.logger.exception("Couldn't stop realtime streaming")
            self._flagCheckStreamer = True
            result = False
        return result


    def _TransferMin1(self):
        tic = time.time()
        try:
#            self.logger.debug("Grouping quotes...")
            mg = minutegrouper.MinuteGrouper()
            mg.SetNames2(self._allNamesEx)
            mg.GroupQuotes()
##            mg.TransferToSecondly()
            # TODO: - Perform a record on the Secondly table, but this time not using the special recording, but the NORMAL ONE
            self._flagTransferedMin1 = True
            na.vala.EmitNewData({"names": self._allNamesEx, \
                "periods": mg.periods})
#            self.logger.debug("_TransferMin1() took %.4g seconds" % (time.time()-tic))
        except:
            self.logger.exception("_TransferMin1() failed")
            # Won't flag a pending operation, this is low priority and,
            # furthermore, exceptions here will be probably due to a bug

    def _DoPause(self, flag):
        if flag == self._flagPaused:
            self.logger.info("Received Pause=%s but flagPause is already %s" % (flag, flag))
            return True
        result = True
        try:
            if flag:
                self._flagWasStreaming = self._flagStreaming
                if self._flagStreaming:
                    if not self._StopStreaming():
                        raise RuntimeError("Couldn't stop streaming")
                self._flagPaused = True
                self.logger.info("_ v _")
            else:
                if self._flagWasStreaming:
                    if not self._StartStreaming():
                        raise RuntimeError("Couldn't start streaming")
                self._flagPaused = False
                self.logger.info("O v O")
            self._flagPendingPause = False
        except:
            self.logger.exception("Couldn't pause")
            self._flagPendingPause = True
            self._pendingPauseData = flag
            result = False
        return result

    def on_RecorderMessage(self, mm):
        """Slot to receive signals sent by the process monitor."""
        token, data = mm
        if token == "Finished":
            self._flagRecorderFinished = True
            self._flagTransferedMin1 = False # Flags to transfer min1 on the next opportunity
        self.logger.debug("Received Recorder message: %s" % token)

